package com.eastedu.util;

import com.eastedu.client.file.model.FileSignatureResponse;
import com.eastedu.exception.ServiceException;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicSessionCredentials;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.StorageClass;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.transfer.TransferManager;
import com.qcloud.cos.transfer.TransferManagerConfiguration;
import com.qcloud.cos.transfer.Upload;

import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author 袁松明
 */
public class CosUtils {
    /**
     * 自定义线程池大小，建议在客户端与 COS 网络充足（例如使用腾讯云的 CVM，同地域上传 COS）的情况下，设置成16或32即可，可较充分的利用网络资源
     */
    private static final int UPLOAD_THREAD_POOL_SIZE = 32;
    /**
     * 分块上传阈值和分块大小分别为 5MB 和 1MB
     */
    private static final long UPLOAD_THRESHOLD = 5 * 1024 * 1024;

    /**
     * 分块上传阈值和分块大小分别为 5MB 和 1MB
     */
    private static final long UPLOAD_PART_SIZE = 1024 * 1024;
    /**
     * 线程池线程名前缀
     */
    private static final String THREAD_POOL_THREAD_NAME_PREFIX = "cos-transfer-manager-worker-";

    /**
     * Cos 自定义头部
     */
    private static final String X_COS_META_MD5_HEADER_KEY = "x-cos-meta-md5";

    /**
     * 上传文件到腾讯云cos
     *
     * @param fileSignature 上传签名
     * @param cosPath       云端文件路径
     * @param localPath     本地文件路径
     */
    public static void uploadFile(FileSignatureResponse fileSignature, String cosPath, Path localPath) {
        String md5 = FileUtils.calcMd5(localPath);
        TransferManager transferManager = createTransferManager(fileSignature);
        PutObjectRequest putObjectRequest = createPutObjectRequest(md5, fileSignature, cosPath, localPath);
        try {
            Upload upload = transferManager.upload(putObjectRequest);
            upload.waitForUploadResult();
        } catch (Exception e) {
            throw new ServiceException(e, "上传文件到腾讯云存储失败: " + e.getMessage());
        } finally {
            shutdownTransferManager(transferManager);
        }
    }

    private static TransferManager createTransferManager(FileSignatureResponse fileSignature) {
        COSClient cosClient = createCosClient(fileSignature);
        // 线程池不确定是否能够共用，腾讯云官方文档就是这样写的，所以每次都新建线程池
        ThreadPoolExecutor threadPool = createThreadPool();
        // 记得设定关闭 TransferManager 的线程池
        TransferManager transferManager = new TransferManager(cosClient, threadPool, true);
        TransferManagerConfiguration transferManagerConfiguration = new TransferManagerConfiguration();
        transferManagerConfiguration.setMultipartUploadThreshold(UPLOAD_THRESHOLD);
        transferManagerConfiguration.setMinimumUploadPartSize(UPLOAD_PART_SIZE);
        transferManager.setConfiguration(transferManagerConfiguration);
        return transferManager;
    }

    private static COSClient createCosClient(FileSignatureResponse fileSignature) {
        // 按照文档COSClient应该是单例的，但是这里的签名信息是每次都不一样的，所以这里每次都创建一个新的COSClient
        String tmpSecretId = fileSignature.getSignature().getTmpSecretId();
        String tmpSecretKey = fileSignature.getSignature().getTmpSecretKey();
        String sessionToken = fileSignature.getSignature().getSessionToken();
        BasicSessionCredentials cred = new BasicSessionCredentials(tmpSecretId, tmpSecretKey, sessionToken);
        Region region = new Region(fileSignature.getHeader().getRegion());
        ClientConfig clientConfig = new ClientConfig(region);
        return new COSClient(cred, clientConfig);
    }

    private static ThreadPoolExecutor createThreadPool() {
        return new ThreadPoolExecutor(UPLOAD_THREAD_POOL_SIZE, UPLOAD_THREAD_POOL_SIZE,
                0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), runnable -> {
            Thread thread = new Thread(runnable);
            thread.setName(THREAD_POOL_THREAD_NAME_PREFIX + thread.getId());
            return thread;
        });
    }

    private static PutObjectRequest createPutObjectRequest(String md5, FileSignatureResponse fileSignature,
                                                           String cosPath, Path localPath) {
        String bucketName = fileSignature.getHeader().getBucket();
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, cosPath, localPath.toFile());
        // 与研发确认，这里的存储类型是标准存储
        putObjectRequest.setStorageClass(StorageClass.Standard_IA);
        ObjectMetadata objectMetadata = buildCosObjectMetadata(md5);
        putObjectRequest.withMetadata(objectMetadata);
        return putObjectRequest;
    }

    private static ObjectMetadata buildCosObjectMetadata(String md5) {
        ObjectMetadata objectMetadata = new ObjectMetadata();
        Map<String, String> userMeta = new HashMap<>(1);
        userMeta.put(X_COS_META_MD5_HEADER_KEY, md5);
        objectMetadata.setUserMetadata(userMeta);
        return objectMetadata;
    }

    private static void shutdownTransferManager(TransferManager transferManager) {
        // 在确定不再通过 TransferManager 的实例调用高级接口之后，一定要关闭这个实例，防止资源泄露。
        if (Objects.nonNull(transferManager)) {
            transferManager.shutdownNow(true);
        }
    }
}
